﻿using System;
using System.Collections.Generic;
using QTPlugin;
using QTPlugin.Interop;
using System.Windows.Forms;
using System.IO;
using System.Drawing;
using Microsoft.Win32;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Runtime.InteropServices.ComTypes;
using System.ComponentModel;
using System.Text.RegularExpressions;
using Kawanon.Usability;
using System.Runtime.Serialization;
using System.IO.Compression;
using System.Runtime.Serialization.Formatters.Binary;
using Kawanon.Usability.Visualization;
using System.Globalization;

namespace Kawanon.QtHoney
{
	class AlreadyAttachedException : ApplicationException
	{
	}

	class WindowNotFoundException : ApplicationException
	{
		//public IntPtr ViewHandle { get; private set; }

		//public ShellViewDoesNotHaveListViewException(IntPtr hwnd)
		//{
		//    ViewHandle = hwnd;
		//}
	}

	public class ArchiveVersionMismatchException : ApplicationException
	{
	}

	public static class Persistence
	{
		private const int ARCHIVE_VERSION_MISCAST = 1;

		[Serializable]
		public struct ArchiveHeader
		{
			public int Version { get; set; }
			public int ObjectCount { get; set; }
		}

		public static void ArchiveFromArray(Stream stream, object[] list)
		{
			DeflateStream deflate = new DeflateStream(stream, CompressionMode.Compress, true);

			BinaryFormatter bin = new BinaryFormatter();
			Array.ForEach(list, obj => bin.Serialize(deflate, obj));

			deflate.Flush();	// フラッシュして閉じないと読み込みで失敗する
			deflate.Close();	//（エンドマークが書き込まれない）
		}

		public static void SaveBinary(string val, object[] list)
		{
			MemoryStream ms = new MemoryStream();
			ArchiveFromArray(ms, list);

			RegistryKey key = Plugins.OpenRegistryKey(true);
			key.SetValue(val, ms.ToArray(), RegistryValueKind.Binary);
			key.Flush();
			key.Close();
		}

		public static Stream CreateValueStream(string name)
		{
			RegistryKey key = Plugins.OpenRegistryKey(false);
			if (key == null)
			{
				return null;
			}

			byte[] bytes = (byte[])key.GetValue(name);
			if (bytes == null)
			{
				return null;
			}
			MemoryStream ms = new MemoryStream(bytes);
			return ms;
		}

		public delegate object Loader();

		public static Loader CreateLoader(Stream istream)
		{
			DeflateStream inflate = new DeflateStream(istream, CompressionMode.Decompress);
			BinaryFormatter bin = new BinaryFormatter();
			bin.Binder = new LocalSerializationBinder();

			return delegate()
			{
				return bin.Deserialize(inflate);
			};
		}

		private static string GetArchiveName(Type t)
		{
			return t.Name.Replace("Information", "");
		}

		public static T GetInformation<T>()
			where T: class
		{
			object info = LoadInformation<T>();
			if (info == null)
			{
				return (T)Activator.CreateInstance(typeof(T));
			}
			return (T)info;
		}

		public static T LoadInformation<T>()
			where T: class
		{
			Stream stream = CreateValueStream(GetArchiveName(typeof(T)));
			if (stream == null)
			{
				return null;
			}
			return (T)LoadInformation(stream);
		}

		public static object LoadInformation(Stream istream)
		{
			Loader loader = CreateLoader(istream);

			int ver = (int)loader();
			if (ver != ARCHIVE_VERSION_MISCAST)
			{
				throw new ArchiveVersionMismatchException();
			}

			return loader();
		}

		public static void SaveInformation(object info)
		{
			Type t = info.GetType();
			string name = GetArchiveName(t);

			SaveBinary(name, new object[] { ARCHIVE_VERSION_MISCAST, info });

			//if (t.Equals(typeof(RenderingInformation)))
			//{
			Plugins.NotifySettingsChanged();
			//}
		}

		private static IEnumerable<T> UnpackArchiveInternal<T>(Stream istream, long pos)
		{
			// IEnumerable の先頭要素に戻ったときの対策
			istream.Seek(pos, SeekOrigin.Begin);

			Persistence.Loader loader = Persistence.CreateLoader(istream);
			ArchiveHeader header = (ArchiveHeader)loader();
			if (header.Version != ARCHIVE_VERSION_MISCAST)
			{
				throw new ArchiveVersionMismatchException();
			}
			for (int i = 0; i < header.ObjectCount; i++)
			{
				T obj = (T)loader();
				yield return obj;
			}
		}

		public static IEnumerable<T> UnpackArchive<T>(Stream istream)
		{
			long pos = istream.Position;
			return UnpackArchiveInternal<T>(istream, pos);
		}

		public static object[] CreateArchive<T>(IEnumerable<T> e)
		{
			List<object> list = new List<object>();
			ArchiveHeader header = new ArchiveHeader();
			header.Version = ARCHIVE_VERSION_MISCAST;
			header.ObjectCount = Utility.CountOf(e);

			list.Add(header);
			foreach (T obj in e)
			{
				list.Add(obj);
			}
			return list.ToArray();
		}
	}

	// QTTabBar でロード時に BinaryFormatter がアセンブリを特定できない問題の対策（GACがらみ？）
	public class LocalSerializationBinder : SerializationBinder
	{
		public override Type BindToType(string assemblyName, string typeName)
		{
			//NOTE: アセンブリから直接タイプ名を取得する
			typeName = typeName.Replace("DefinedCommand", "BuiltInCommand");

			if (typeName.EndsWith("ExpressionArchiveHeader"))
			{
				typeName = typeof(Persistence.ArchiveHeader).FullName;
			}

			Type ty = Assembly.GetExecutingAssembly().GetType(typeName);
			//DebugHelpers.Echo(ty +"_ "+ typeName +"_ "+ assemblyName);

			//StringBuilder buf = new StringBuilder();
			//foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
			//{
			//    buf.Append(asm.FullName);
			//    buf.Append("\r\n");
			//}
			//DebugHelpers.Echo(buf);
			return ty;
		}
	}

	public class PathExpander
	{
		private static Regex s_regCurrentFolder = new Regex("%c%", RegexOptions.Compiled | RegexOptions.IgnoreCase);
		private static Regex s_regFolders = new Regex("%d%", RegexOptions.Compiled | RegexOptions.IgnoreCase);
		private static Regex s_regSelectionOrCurrentFolder = new Regex("%cd%", RegexOptions.Compiled | RegexOptions.IgnoreCase);
		private static Regex s_regFiles = new Regex("%f%", RegexOptions.Compiled | RegexOptions.IgnoreCase);
		private static Regex s_regSelection = new Regex("%s%", RegexOptions.Compiled | RegexOptions.IgnoreCase);

		private string m_currentFolder;
		private string m_selectionOrCurrentFolder;
		private string m_strFiles = String.Empty, m_strFolders = String.Empty, m_strSelection = String.Empty;

		public PathExpander(Address currentFolder, Address[] selection)
		{
			m_currentFolder = Utility.Quote(Utility.AddressToPath(currentFolder));
			m_currentFolder = m_currentFolder != null ? m_currentFolder : String.Empty;

			GenerateStrings(selection);

			m_selectionOrCurrentFolder = (m_strFolders != String.Empty) ? m_strFolders : m_currentFolder;
		}

		private string Concat(string[] paths)
		{
			return String.Join(" ", Array.ConvertAll(paths, path => Utility.Quote(path)));
		}

		private void GenerateStrings(Address[] addrs)
		{
			if (addrs == null)
			{
				return;
			}

			string[] paths = Array.ConvertAll<Address, string>(addrs, Utility.AddressToPath);

			paths = Array.FindAll(paths, path => !String.IsNullOrEmpty(path));
			// 仮想フォルダも含める
			string[] folders = Array.FindAll(paths, path => !File.Exists(path));
			string[] files = Array.FindAll(paths, path => Array.IndexOf(folders, path) == -1);

			m_strSelection = Concat(paths);
			m_strFiles = Concat(files);
			m_strFolders = Concat(folders);
		}

		public string Replace(string str)
		{
			if (str == null)
			{
				return null;
			}
			str = s_regSelection.Replace(str, m_strSelection);
			str = s_regFiles.Replace(str, m_strFiles);
			str = s_regFolders.Replace(str, m_strFolders);
			str = s_regCurrentFolder.Replace(str, m_currentFolder);
			str = s_regSelectionOrCurrentFolder.Replace(str, m_selectionOrCurrentFolder);
			return str;
		}
	}

	public class GroupMenuFactory	// GroupEditor でも使う？ (Server == null でメニューを無効にして)
	{
		public IPluginServer Server { get; set; }
		public bool Expandable { get; set; }
		//public ImageList ImageList { get; set; }

		private ToolStripMenuItem CreateGroupItem(string name, string[] paths)
		{
			ToolStripMenuItem group = new ToolStripMenuItem(name);
			if (paths.Length > 0)
			{
				group.Image = Utility.GetIconBitmap(paths[0], false);
			}

			EventHandler handler = new EventHandler(delegate(object sender, EventArgs e)
			{
				if (Server != null)
				{
					Server.OpenGroup(new string[] { name });
				}
				//group.HideDropDown();	// これは効果が無い？
				group.DropDown = null;
				group.Owner.Hide();
			});

			if (Expandable)
			{
				//group.DoubleClickEnabled = true;
				//group.DoubleClick += handler;
				group.Click += handler;

				foreach (string path in paths)
				{
					ToolStripMenuItem child = new ToolStripMenuItem(
						Utility.IsSpecialFolder(path) ? Utility.GetDisplayName(path) : path);
					//BUG: ImageIndex を指定しても描画されない
					child.Image = Utility.GetIconBitmap(path, false);
					Address addr = new Address(path);	// path を使うと最後の要素になってしまう
					child.Click += new EventHandler(delegate(object sender, EventArgs e)
					{
						if (Server != null)
						{
							Server.CreateTab(addr, -1, false, true);
						}
					});
					group.DropDownItems.Add(child);
				}
			}
			else
			{
				group.Click += handler;
			}
			return group;
		}

		public void CreateItems(ToolStripItemCollection items)
		{
			RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Software\Quizo\QTTabBar\Groups", false);
			foreach (string name in key.GetValueNames())
			{
				string val = key.GetValue(name) as string;
				if (val == null)
				{
					continue;
				}
				if (val == String.Empty)
				{
					items.Add(new ToolStripSeparator());
					continue;
				}

				string[] paths = val.Split(';');
				ToolStripMenuItem group = CreateGroupItem(name, paths);
				items.Add(group);
			}
			key.Close();
		}
	}

	public static class Plugins
	{
		// サーバはウィンドウごとに異なる
		//public static IPluginServer Server { get; set; }
		public static List<IPluginClient> Instances { get; private set; }

		private static FieldInfo GetNowDebuggingField()
		{
			return Type.GetType(
				"QTTabBarLib.QTUtility, QTTabBar, Version=1.0.0.0, Culture=neutral, PublicKeyToken=78a0cde69b47ca25"
					).GetField("NowDebugging", BindingFlags.NonPublic | BindingFlags.Static);
		}

		public static bool DebugMode
		{
			get
			{
				FieldInfo field = GetNowDebuggingField();
				if (field == null)
				{
					return false;
				}
				return (bool)field.GetValue(null);
			}
			set
			{
				FieldInfo field = GetNowDebuggingField();
				if (field == null)
				{
					return;
				}
				field.SetValue(null, value);
			}
		}

		static Plugins()
		{
			Instances = new List<IPluginClient>();
		}

		public static RegistryKey OpenRegistryKey(bool writable)
		{
			return OpenRegistryKey(typeof(Plugins), writable);
		}

		private static RegistryKey OpenRegistryKey(Type t, bool writable)
		{
			string ns = t.Namespace.Replace('.', '\\');
			string key = String.Format(@"Software\Quizo\QTTabBar\Plugins\Settings\{0}", ns);
			if (writable)
			{
				return Registry.CurrentUser.CreateSubKey(key);
			}
			else
			{
				return Registry.CurrentUser.OpenSubKey(key);
			}
		}

		public static RegistryKey OpenClassRegistryKey(Type t, bool writable)
		{
			RegistryKey parent = OpenRegistryKey(writable);
			if (writable)
			{
				return parent.CreateSubKey(t.Name);
			}
			else
			{
				if (parent == null)
				{
					return null;
				}
				return parent.OpenSubKey(t.Name);
			}
		}

		public static event EventHandler SettingsChanged;

		public static void NotifySettingsChanged()
		{
			Utility.Raise(SettingsChanged);
		}
	}

	//public class BarCustomItem : BarButton, IBarCustomItem
	//{
	//    public ToolStripItem CreateItem(bool fLarge, QTPlugin.DisplayStyle displayStyle)
	//    {
	//        return null;
	//    }
	//}


}
